Hi! Ich hei~e Face Hugger und habe mich von GWM breitschlagen lassen ⇩in der CPI einen Assemblerkurs aufzuziehen. Da ich momentan bei der BW ⇩bin und gerade nichts besseres zu tun habe, sitze ich hier auf meiner ⇩"Stube" und schreibe diesen Mist. Ich wei~ garnicht genau, wie ich ⇩euch den Senf beibringen soll, aber ich hoffe, da~ sich dieser Kurs ⇩positiv entwickelt. ]ber Kritik und Anregungen w}rde ich mich freuen. ⇩
Als erstes stellt sich die Frage der Zielgruppe. Assemblerkenntnisse ⇩sind nicht notwendig. Ihr solltet euch allerdings etwas mit Bits und ⇩Bytes auskennen, Hexadezimalzahlen halbwegs beherrschen, vielleicht ⇩etwas }ber die Speicheraufteilung des CPC wissen und in Basic mehr ⇩zustandekriegen wie - 10 PRINT"Hallo ich heisse AOS!":GOTO 10 -
Zum Assembler: Ich benutze den MAXAM-Assembler und werde mich in den ⇩Erkl{rungen auf diesen beschr{nken.
Auf geht's:
Die Register
Die Register sind die Variablen der Maschinensprache. Ich beschr{nke ⇩mich ersteinmal auf die wichtigsten Register A, B, C, D, E, F, H, L. ⇩Diese Register sind alle 8-Bit gro~ (oder breit, tief, hoch...). A ist ⇩der sogenannte Akkumulator. Viele Assemblerbefehle (Operationen) ⇩lassen sich nur mit ihm ausf}hren. F}r 16-Bit-Operationen werden die ⇩Register B+C, D+E und H+L zu sogenannten Doppelregister oder auch ⇩Registerpaaren zusammengegfa~t. Sie lauten dann eben BC, DE und HL, ⇩wobei HL wieder eine besondere Stellung einnimmt, da einige Befehle ⇩nur mit HL m|glich sind
Die ersten Befehle
Der CALL-Befehl: Diesen Befehl gibt es genauso in Basic. Er ruft ein ⇩Maschinenprogramm auf, {hnlich wie mann mit GOSUB in Basic ein ⇩Basic-Unterprogramm aufruft. Die Stelle im Speicher, wo das ⇩Maschinenprogramm anf{ngt wird als sogenannter Parameter hinter dem ⇩CALL angegeben. Diese "Stelle im Speicher" ist praktisch die ⇩Zeilennummer im Basic, nur da~ sie nicht Zeilennummer hei~t, sondern ⇩"Adresse" (hat nichts mit 'ner Wohnung zu tun). An der Adresse BB06 ⇩liegt ein Maschinenprogramm, da~ auf einen Tastendruck wartet. Mit dem ⇩CALL-Befehl k|nnen wir es aufrufen. Gebt in Basic ein:
CALL &BB06
Der Rechner wartet auf einen Tastendruck, es funktioniert also.
Der RET-Befehl: Am Ende jedes Maschinenprogramms steht meistens ein ⇩RET-Befehl, der nichts anderes zu bedeuten hat, wie der RETURN-Befehl ⇩in Basic. Er zeigt das Ende eines Maschinen(unter-)programms an. Der ⇩Computer springt anschlie~end wieder zur}ck zum Hauptprogramm, wo der ⇩CALL-Befehl ausgef}hrt wurde.
Der LD-Befehl: Allgemein gesehen der meistgebrauchte Befehl; der ⇩Lade-Befehl. Der Syntax lautet LD <Ziel>,<Quelle>.
LD A,B hei~t z.B. "Lade A mit B", in Basic w{re das "A=B". Dieser ⇩Befehl ist in vielf{ltiger Form auffindbar: LD D,E LD L,A LD C,L ⇩usw. . Man kann auch ein Register mit einem Wert (einer Konstanten) ⇩laden, z.B. LD B,10.
Unser erstes Maschinenprogramm:
Maxam starten (Assembler & Editor), IM eingeben, <T>exteditor, <E>dit ⇩Text. Nun gebt folgendes Programm ein:
ORG &5000 ;dies ist kein Assemblerbefehl, sondern ein ⇩ ;Hinweis, da~ der assemblierte Code bei der ⇩ ;Adresse &5000 anfangen soll.
LD A,2 ;A=2
CALL &BC0E ;BC0E -> Mode-Befehl. Im Akku steht der ⇩ ;Bildschirmmode, hier Mode 2
RET ;fertig
Wenn ihr dies eingetippt habt, ESC um ins Men} zur}ckzukommen und <A> ⇩f}r "Assemble" dr}cken, das Programm wird assembliert, Taste dr}cken ⇩um ins Men} zur}ckzukommen. Nun <J> dr}cken f}r "Jump to code". Unser ⇩Progr{mmchen steht bei &5000, also 5000 eingeben. Siehe da, Mode 2 is ⇩born!
Weiteres zum LD-Befehl: Ihr habt bisher erst zwei Varianten des ⇩Lade-Befehls kennengelernt, n{mlich LD Register,Register und ⇩LD’Register,Wert. Es gibt noch viele andere M|glichkeiten:
LD (Adresse),A ;Lade A in die Speicherstelle "Adresse"
LD A,(Adresse) ;Lade A aus der Speicherstelle "Adresse"
genauso:
LD (HL),A
LD A,(HL)
LD (DE),A
LD A,(DE)
LD (BC),A
LD A,(BC)
hier wird lediglich die Adresse an die/aus der der Akku geladen wird ⇩durch ein 16-Bit-Register angegeben.
Beispiel:
ORG &5000
LD A,255 ;A=255
LD (&C000),A ;A nach C000 laden, bei C000 f{ngt der ⇩ ;Bildschirmspeicher an
RET ;fertig
Wenn ihr das Ding mal startet, m}~te links oben auf dem Bildschirm ein ⇩kleiner l{nglicher Strich erscheinen. Der Wert 255 befindet sich nun ⇩an der Adresse C000.
LD Doppelregister,16-Bit-Wert
Mit diesem Befehl k|nnen wir den Lade-Befehl LD (HL),A testen.
ORG &5000
LD A,255 ;A=255
LD HL,&C000 ;HL=C000 (nebenbei bemerkt: Die 8-Bit-Register ⇩ ;einzeln betrachtet ist H=C0 und L=00, H (bzw. ⇩ ;D oder B) ist das sog. Highbyte und L (bzw. E ⇩ ;oder C) das Lowbyte.
LD (HL),A ;Lade A nach (HL), also an die Adresse aus HL, ⇩ ;also nach C000
RET
Nun, wie zu Beginn erw{hnt, zu der besonderen Stellung des ⇩HL-Registers:
Neben LD (HL),A ist genauso LD (HL),B; LD (HL),E; LD (HL),H m|glich. ⇩Bei DE und BC existiert der Befehl nur mit A, LD (BC),E ist z.B. nicht ⇩m|glich!!
LD (Adresse),A ist auch nur mit dem Akku m|glich LD (Adresse),C gibt's ⇩nicht!
So das war's erstmal. Ist schon irgendjemand nicht mitgekommen? Im ⇩n{chsten Teil werde ich euch wieder ein paar neue Befehle erkl{ren und ⇩etwas }ber Flags erz{hlen. Au~erdem stelle ich euch mein erstes ⇩Assemblerprogramm vor (mehr wie 3 Jahre alt), von dem ich so ⇩hingerissen war, da~ ich mir in einer Woche den ganzen Assemblersumpf ⇩reingeschl}rft habe.
Assemblerkurs Teil 2
Gebt mal in BASIC ein: MODE 2:FOR I=&C000 TO &C0FE:POKE i,255:NEXT
Langsam aber sicher erscheinen ein paar Linien auf dem Screen. Wie das ⇩ganze in Assembler aussieht, zeige ich euch gleich, erstmal ein paar ⇩neue Befehle:
INC register
das Register wird um eins erh|ht (increment)
DEC register
das Register wird um eins verringert (decrement)
DEC A w{re z.B. A=A-1 in BASIC. Der Befehl ist genauso auf
16-Bit-Register anwendbar z.B. INC HL oder DEC BC.
JP Adresse
Springe {hnlich dem CALL-Befehl zur angegebenen Adresse. In BASIC ⇩vergleichbar mit dem GOTO-Befehl. Der Unterschied zum CALL-Befehl ⇩liegt darin, da~ hier nicht mit einem RET-Befehl zur}ckgesprungen ⇩werden kann.
JR Sprungdifferenz
JR ist im Prinzip genauso wie der JP-Befehl, nur da~ nun relativ zur ⇩aktuellen Adresse gesprungen wird. H|rt sich kompliziert an, ist aber ⇩recht einfach: JR 50 hei~t z.B. 'springe um 50 Bytes weiter'. Dies hat ⇩zwei Vorteile:
1. der Befehl ist um ein Byte k}rzer als der JP-Befehl.
2. Programme, die nur JPs statt JRs benutzen, laufen auch nur an der ⇩ Adresse, an der sie assembliert wurden. Programme, die nur JRs ⇩ benutzen sind an jeder beliebigen Adresse lauff{hig (andere ⇩ Befehle, die mit absoluten Adressen arbeiten d}rfen dann nat}rlich ⇩ auch nicht verwendet werden).
Nachteil: die Sprungdifferenz ist nur -128 bis +127 gro~.
Um das Ausrechnen der Sprungdifferenz k}mmert sich nat}rlich der ⇩Assembler.
Die Flags:
Die Flags sind einzelne Bits im bisher noch nicht besprochenen
F-Register. Diese Bits werden durch einige Befehle beeinflu~t. Die ⇩beiden wichtigsten Flags sind das Carry-(]bertrags-)Flag und das
Zero-Flag. Das Zero-Flag wird dann gesetzt, wenn z.B. beim
INC/DEC-Befehl mit 8-Bit-Registern das entsprechende Register den Wert ⇩Null erreicht.
Bei den Sprung befehlen (CALL, RET, JP, JR) lassen sich Bedingungen ⇩ankn}pfen, so da~ der Computer auf die Flagzust{nde reagieren kann, ⇩{hnlich wie IF THEN GOTO in BASIC.
Die Bedingungen lauten Z (springe wenn Null (Zero)), NZ (springe wenn ⇩nicht Null (Not Zero)), C (springe bei ]bertrag (Carry)), NC (springe ⇩wenn kein ]bertrag (Not Carry)). Als Assemblersyntax sieht das so aus: ⇩CALL NZ,Adresse oder z.B. RET Z.
Am besten versteht ihr das an einem zusammenh{ngendem Programm:
Mein erstes Assemberprogramm
ORG &5000
LD B,255 ;B ist der Schleifenz{hler, B=255
LD HL,&C000 ;HL enth{lt die Bildschirmadresse, HL=&C000
LD E,255 ;der Wert 255 soll in den Bildschirm gepoket ⇩ ;werden also E=255
loop LD (HL),E ;E (255) in den Bildschirm keulen
INC HL ;HL (Bildschirmadresse um eins erh|hen
DEC B ;B=B-1 -> Zero-Flag wird gesetzt wenn B=0 ist
JR NZ,loop ;Springe nach 'loop' wenn Zero-Flag nicht ⇩ ;gesetzt (B ungleich Null)
RET ;fertig
Wenn ihr das mit dem Basicprogramm vergleicht, werdet ihr einen ⇩enormen Geschwindigkeitsunterschied feststellen. 'loop' ist ein sog. ⇩Label, eine Art Sprungmarke. Man kann solche Labels auch zu Beginn ⇩definieren. Unser MODE 2 Progr{mmchen aus dem 1.Teil dieses Kurses ⇩k|nnte z.B. so aussehen:
StMode EQU &BC0E
ORG &5000
LD A,2
CALL StMode
RET
Nebenbei bemerkt: Bei diesen paar Assemblerzeilen w}rden die Profis ⇩schon aufschreien, da man das CALL und das nachfolgende RET zu einem ⇩JP zusammenfa~t:
LD A,2
JP StMode
macht n{mlich dasselbe und spart ein Byte, oder wie ein altes ⇩bayrisches Sprichwort sagt: CALL auf RET doas gibtes net!
Genauso ist in BASIC die Befehlsfolge GOSUB 1000:RETURN v|lliger ⇩Quatsch, da hier ein GOTO 1000 hingeh|rt.
Als Schleifenz{hler kann theoretisch jedes andere der gebr{uchlichen ⇩8-Bit-Register benutzt werden. F}r solche Schleifen stellt der Z80 ⇩allerdings einen speziellen Befehl zur Verf}gung:
DJNZ Sprungdifferenz (bzw. in Assembler steht hier ein Label)
Dieser Befehl tut nichts anderes wie die Befehlsfolge
DEC B
JR NZ,loop
im Beispiellisting, ist aber ein Byte k}rzer und entsprechend ⇩schneller. Der DJNZ-Befehl benutzt das B-Register, deshalb wird dieses ⇩Register meistens als Schleifenz{hler benutzt. Zum Testen k|nnt ihr ⇩mal die besagten zwei Zeilen durch ein 'DJNZ loop' ersetzen.
Nun mein erstes Progr{mmchen ist zwar sch|n, aber ziemlich sinnlos. ⇩Man sollte vielleicht mal den ganzen Screen f}llen:
Der Bildschirmspeicher ist &4000 gro~. Da ein 8-Bit-Register als ⇩Schleifenz{hler nicht ausreicht, m}ssen wir diesesmal mit einem ⇩Doppelregister arbeiten.
Wir m}ssen also einfach den Befehl LD B,255 durch ein LD BC,&4000 und ⇩den Befehl DEC B durch DEC BC ersetzen. Doch da gibt es ein Problem: ⇩Wie bereits einmal angedeutet, beeinflu~t nur der INC/DEC-Befehl mit ⇩8-Bit-Registern das Zero-Flag. d.h. bei einem DEC BC w}rde der ⇩anschlie~ende Sprungbefehl JR NZ nicht korrekt funktionieren.
Um zu testen ob ein 16-Bit-Register Null ist, bedienen wir uns dem ⇩OR-Befehl:
Syntax: OR 8-Bit-Register
Rein {u~erlich fand ich diesen Befehl fr}her als Assembler-Rookie ⇩ziemlich verwirrend, da er nur einen Parameter besitzt. Es ist aber ⇩ganz einfach: Solche Operationen werden immer mit dem Akku ausgef}hrt, ⇩und das Ergebnis wird immer im Akku gespeichert. Der Befehl OR ⇩bedeutet wie im BASIC eine Oder-Verkn}pfung
OR B hei~t z.B. A=A OR B. Der Akku wird mit dem B-Register ⇩ODER-Verkn}pft, und das Ergebnis wird im Akku gespeichert. F}r unser ⇩Beispiel wichtig: die Flags werden beeinflu~t.
Um nun das 16-Bit-Register BC auf Nullheit abzutesten, sind folgende ⇩zwei Zeilen notwendig
LD A,B ;Lade A mit B
OR C ;ODER-Verkn}pfung A mit C
Im Endeffekt ist dies eine ODER-Verkn}pfung zwischen B und C.
B ODER C bedeutet: Ist in B ODER in C irgendein Bit gesetzt, dann sind ⇩auch im Ergebnis entsprechend Bits gesetzt.
B: 01000010
ODER C: 00011010
---------------------
Ergebnis: 01011010
Wie ihr seht, taucht nur dort ein Null-Bit auf, wo in B und C ein ⇩Null-Bit steht. Das Ergebnis ist also nur dann Null, wenn B und C ⇩gleich Null sind.
Alles zusammengefa~t sieht das Assemblerlisting zum Screenf}llen nun ⇩so aus:
ORG &5000
LD BC,&4000 ;BC ist der 16-Bit-Schleifenz{hler, BC=&4000
LD HL,&C000 ;HL ist die Bildschirmadresse, HL=&C000
LD E,255 ;E ist das F}llbyte, E=255
loop LD (HL),E ;E in den Screen schmei~en
INC HL ;Adresse auf dem Screen um eins erh|hen
DEC BC ;Schleifenz{hler um eins verringern
LD A,B ;Schleifenz{hler...
OR C ;...ungleich Null?
JR NZ,loop ;dann nach 'loop'
RET ;fertig
Nebenbei bemerkt: den Schleifenz{hler k|nnte man sich in diesem Fall ⇩sparen, da der Bildschirmspeicher bei &0000 aufh|rt (&FFFF+1=&0000).
Optimiert m}~te das Listing dann so aussehen:
ORG &5000
LD HL,&C000 ;HL=&C000
LD E,255 ;E=255
loop LD (HL),E ;(HL)=E
INC HL ;HL=HL+1
LD A,H
OR L ;HL ungleich 0 ?
JR NZ,loop ;dann nach 'loop'
RET ;fertig
So das war's erstmal. Im n{chsten Teil werde ich euch wahrscheinlich ⇩etwas }ber Addition und Subtraktion erz{hlen und n{her auf die ⇩logischen Verkn}pfungen (like OR) eingehen.
Assemblerkurs Teil 3
Wie Versprochen nun etwas zu den Additions- und Subtraktionsbefehlen ⇩des Z80:
Auf 8-Bit-Ebene:
ADD 8-Bit-Register
Das entsprechende 8-Bit-Register wird auf das Akku addiert. Das ⇩Ergebnis wird im Akku gespeichert. Die Flags werden beeinflu~t. ⇩]berschreitet das Ergebnis den 8-Bit-Bereich, so wird ein ]bertrag ⇩erzeugt (Carry=1).
ADD H ist z.B. A=A+H
genauso funktioniert auch die Addition mit einer Konstanten
ADD 29 ist z.B. A=A+29
Will man B=B+11 rechnen, so mu~ man erst das B-Register ins Akku ⇩laden, 11 addieren und dann das Ergebnis wieder nach B zur}ckladen:
LD A,B
ADD 11
LD B,A
Au~erdem gibt es noch den Befehl 'ADC 8-Bit-Register'. Er funktioniert ⇩genauso wie der ADD-Befehl, nur da~ hier zus{tzlich noch das Carry ⇩aufaddiert wird. Mit diesem Befehl l{~t sich eine 16-Bit-Addition ⇩realisieren:
BC=BC+DE:
LD A,C
ADD E ;Lowbytes addieren
LD C,A
LD A,B
ADC D ;Highbytes addieren unter Ber}cksichtigung des ]bertrags
LD B,A
Alle Befehle funktionieren genauso in der Subtraktion. Sie hei~en SUB ⇩bzw. SBC. Ein ]bertrag wird hier erzeugt, wenn das Ergebnis Null ⇩unterschreitet.
Addition & Subtraktion mit 16-Bit:
Hierzu gibt es die Befehle:
ADD HL,HL ;HL=HL+HL (HL*2)
ADD HL,DE ;HL=HL+DE
ADD HL,BC ;HL=HL+BC
ADD HL,SP ;HL=HL+SP (SP= Stackpointer, kommt noch)
ADD IX,IX ;IX=IX+IX (IX= Indexregister, kommt n{chstesmal)
ADD IX,DE ;IX=IX+DE
ADD IX,BC ;IX=IX+BC
ADD IX,SP ;IX=IX+SP
ADD IY,IY ;IY=IY+IY (IY= ebenfalls Indexregister)
ADD IY,DE ;IY=IY+DE
ADD IY,BC ;IY=IY+BC
ADD IY,SP ;IY=IY+SP
Das sind alle 16-Bit-ADD-Befehle, sie existieren genauso als
ADC-Befehle.
Subtraktion (hier gibt's kein SUB(!)):
SBC HL,HL ;HL=HL-HL-Carry(!)
SBC HL,DE ;HL=HL-DE-Carry(!)
SBC HL,BC ;HL=HL-BC-Carry(!)
SBC HL,SP ;HL=HL-SP-Carry(!)
Vorsicht: Da der Befehl SUB (ohne Carry) nicht existiert mu~ vor dem ⇩SBC-Befehl das Carry-Flag gel|scht werden, da sonst evtl. eins zuviel ⇩abgezogen wird. Hierzu benutzen wir den Befehl OR A (A=A OR A). Er ⇩ver{ndert den Akku nicht und l|scht das Carry-Flag. So f}hrt also nur ⇩die Befehlsfolge
OR A
SBC HL,DE
zu einer Korrekten 16-Bit-Subtraktion HL=HL-DE.
Wie auch bei den 8-Bit-Additions- und Subtraktionsbefehlen wird auch ⇩bei 16-Bit bei }ber- bzw. unterschreiten des 16-Bit-Bereichs das ⇩Carry-Flag auf 1 gesetzt.
Bin{re Logik: AND, OR, XOR
Diese Befehle existieren nur auf 8-Bit-Ebene. Die Operationen ⇩erfolgen, wie schonmal erw{hnt, immer mit dem Akku.
Allgemein: XOR 8-Bit-Register bzw. 8-Bit-Wert
hei~t A=A XOR 8-Bit-Register bzw. 8-Bit-Wert
genauso OR und AND.
Was hei~t eigentlich A=A OR/AND/XOR Register?
Der AND-Befehl:
Beispiel: A=10110001
B=10010010
AND B (A=A AND B):
Die Bits der beiden Register werden nun Ziffer f}r Ziffer verglichen ⇩und nach folgendem Schemna }bersetzt:
Bit x aus Register A * Bit x aus Register B * Ergebnis
d.h. nur wenn in A und (and!) in B das jeweils verglichene Bit gesetzt ⇩ist (=1!), dann ist auch das jeweilige Bit im Ergebnis gesetzt.
Bit 7: A: (1)0 1 1 0 0 0 1
B: (1)0 0 1 0 0 1 0
Ergebnis A: 1 x x x x x x x
Bit 6: A: 1(0)1 1 0 0 0 1
B: 1(0)0 1 0 0 1 0
A: 1 0 x x x x x x
Bit 5: A: 1 0(1)1 0 0 0 1
B: 1 0(0)1 0 0 1 0
A: 1 0 0 x x x x x
...usw...
Bit 0: A: 1 0 1 1 0 0 0(1)
B: 1 0 0 1 0 0 1(0)
A: 1 0 0 1 0 0 0 0
Wir sehen also: Nur wo in A und B eine '1' steht, steht auch im ⇩Ergebnis eine '1'.
Der OR-Befehl:
Beim OR-Befehl werden die Bits nach folgenden Schema verglichen:
Bit in A * Bit in B * Erg.
********************************
0 * 0 * 0
0 * 1 * 1
1 * 0 * 1
1 * 1 * 1
Nur wo in A oder (or!) in B das jeweilige Bit gesetzt ist, ist auch ⇩das Ergebnis-Bit gesetzt.
OR B (A=A OR B):
A: 10110001
OR B: 10010010
==================
Erg. A: 10110011
Der XOR-Befehl (Exclusives Oder):
Schema:
Bit in A * Bit in B * Erg.
********************************
0 * 0 * 0
0 * 1 * 1
1 * 0 * 1
1 * 1 * 0
Das entsprechende Bit im Ergebnis ist dann gesetzt, wenn die ⇩Bitzust{nde in A und B ungleich sind:
XOR B
A: 10110001
XOR B: 10010010
==================
Erg. A: 00100011
Nun fragt ihr euch, was man mit diesen Befehlen anfangen kann. Hier ⇩ein kurzer Anri~:
AND: l|schen von Bits (ausmaskieren) und abtesten von Bits
AND %11110000 l|scht z.B. die untere H{lfte (Low-Nibble) des Akkus. ⇩Das %-Zeichen ist in Maxam das Zeichen daf}r, da~ eine Bin{rzahl folgt ⇩(wie '&' bei Hexadezimalzahlen). In BASIC ist die '&X'.
Da die oberen vier Bits (High-Nibble) }brig bleiben, k|nnte es auch in ⇩diesem Fall ein Test dieser Bits sein, da nat}rlich das Zero-Flag ⇩beeinflu~t wird (Carry wird immer gel|scht).
OR: Setzen von Bits
OR %11000000 setzt z.B. die obersten beiden Bits des Akkus.
XOR: Wird h{ufig bei Sprite-Routinen benutzt, die die ⇩Hintergrundgrafik nicht zerst|ren sollen.
Unser altes Beispiel:
A: 10110001 (Byte aus der Hintergrundgrafik)
B: 10010010 (Byte aus Sprite (reimt sich!))
(XOR B) A: 00100011 (Neues Bildschirmbyte)
... Sprite ist gesetzt...
A: 00100011 (Bildschirmbyte)
B: 10010010 (Byte aus Sprite (reimt sich immernoch!))
(XOR B) A: 10110001 (Altes Byte aus der Hintergrundgrafik)
... Sprite ist gel|scht...
Man braucht nur eine Routine zum Setzen, da ein weiteres setzen das ⇩Sprite wieder l|scht.
Solche Sprites sehen nat}rlich ver{ndert aus wenn sie }ber eine Grafik ⇩huschen. Kennt ihr bestimmt aus einigen Games.
Puh! Das war's erstmal. N{chstesmal vielleicht was }ber ⇩Indexregister...
Assemblerkurs Teil 4
Indexregister
Wie im letzten Teil kurz angesprochen, besitzt der Z80 weitere
16-Bit-Register, die sogenannten Indexregister, sie‘ hei~en IX und IY. ⇩Ich beschr{nke mich bei der Beschreibung erstmal auf IX, IY ist ⇩genauso.
LD IX,16-Bit-Wert funktioniert genauso wie LD HL,16-Bit-Wert
Wir hatten gelernt, da~ man mit 'LD 8-Bit-Register,(HL)' bzw.
'LD (HL),8-Bit-Register' einen Wert in die Speicherstelle ⇩schreiben/lesen kann, die durch HL angegeben wird.
Mit den Indexregistern funktioniert das genauso, nur da~ man noch ⇩einen Offset angeben kann. Der allgemeine Syntax lautet:
LD 8-Bit-Register,(IX+Offset) bzw. LD (IX+Offset),8-Bit-Register
Beispiel: LD IX,&A000
LD A,(IX+5)
Diese Befehlsfolge bezweckt, da~ der Akku mit dem Wert aus der ⇩Speicherstelle bei A005 geladen wird. Wozu ist das gut?
Durch die Indexregister ist der Zugriff auf Tabellen wesentlich ⇩leichter. Beispiel:
In einem Spiel f}r mehrere Personen werden alle Variablen (alles was ⇩die Spieler unterscheidet) in einer Tabelle gespeichert. Das k|nnen ⇩sein: Anzahl der Leben, Score, X- und Y-Positionen, Spritenummern, ⇩Timer oder auch Adressen von Unterroutinen, z.B. f}r die Tastatur- ⇩bzw. Joystickabfrage. Nun wird das ganze Spiel so geschrieben, da~ es ⇩}ber die Indexregister auf die Variablen zugreift. Der Spieler soll ⇩z.B. 100 Punkte bekommen:
LD DE,100 ;DE=100
LD L,(IX+8) ;Score nach HL
LD H,(IX+9) ;...
ADD HL,DE ;Score=Score+100
LD (IX+8),L ;Score wieder speichern
LD (IX+9),H ;...
Die Hauptschleife sieht dann so aus:
start: LD IX,PlayerTabelle1
CALL Hauptroutine
LD IX,PlayerTabelle2
CALL Hauptroutine
CALL Frame
JP start
Eine kleine Erg{nzung zu den bisher gelernten Befehlen:
Wenn ihr das Byte an der Speicherstelle, die durch HL angegeben wird ⇩um eins verringern wollt, so m}~t ihr nach dem bisher Gelernten ⇩folgendes Schreiben:
LD A,(HL)
DEC A
LD (HL),A
dies k|nnt ihr euch sparen, denn ein DEC (HL) tut's auch (ganz ⇩unflexibel ist der Z80 ja nun doch nicht). Genauso funktioniert
OR (HL), AND (HL), XOR (HL), ADD (HL), ADC (HL), SUB (HL), SBC (HL), ⇩INC (HL) und einige weitere Befehle, die ihr noch kennenlernt.
Genauso funktioniert's mit den Indexregistern, also OR (IX+Offs),
INC (IX+Offs) usw. . Mit BC und DE geht's nat}rlich nicht! DEC (BC) ⇩oder AND (DE) ist utopie! (wie war das noch mit der Flexibilit{t?!).
Stackpointer, PUSH, POP:
Ein weiters Register, da~ ihr noch nicht kennt, ist der Stackpointer ⇩(SP-Register). Beim Z80 passiert es euch schnell, da~ euch die ⇩Register ausgehen. Wenn ihr ein Unterprogramm aufruft das eure ⇩belegten (Brote?) Register ver{ndert, so m}ssen diese irgendwo ⇩zwischengespeichert werden. Dies geht sehr schnell mit dem PUSH- und ⇩POP-Befehlen. Sie hei~en PUSH BC, PUSH DE, PUSH HL, PUSH IX, PUSH IY ⇩und PUSH AF bzw. POP BC, POP DE, usw. .
Bei einem PUSH-Befehl wird das entsprechende 16-Bit-Register nach
(SP-1) und (SP-2) gespeichert und das SP-Register um zwei verringert. ⇩Beim POP-Befehl geschieht dasselbe, blo~ umgekehrt. Die Daten werden ⇩von (SP) und (SP+1) in das entsprechende 16-Bit-Register geladen und ⇩der Stackpointer wird um zwei erh|ht. Wie der Name 'Stack' schon sagt, ⇩werden die Daten quasi gestapelt (Stackpointer=Stapelzeiger). Wenn die ⇩Daten mit dem POP-Befehl wiedergeholt werden sollen, so mu~ der Stapel ⇩in der umgekehrten Reihenfolge des PUSHens wieder abgebaut werden. ⇩Eine Unterroutine s{he dann so aus:
Unterroutine: PUSH HL
PUSH DE
PUSH BC ;HL,DE und BC retten
...
blablabla ;Routine
...
POP BC
POP DE
POP HL ;BC,DE und HL wiederholen
RET
Das Prinzip nennt man LIFO (Last In -> First Out, das letzte, was du ⇩auf den Stapel packst, kommt auch als erstes wieder runter).
In dem ganzen Stapelzeigergewurschtel liegt auch eine schwerwiegende ⇩Absturzursache begraben. Wie ich euch schonmal erkl{rt habe, ruft ein ⇩CALL ein Unterprogramm auf und ein RET springt an die alte Adresse ⇩zur}ck, Irgendwo mu~ sich der Computer ja diese R}cksprungadresse ⇩merken. Und wo macht er das? Richtig! Auf dem Stack! Ein CALL f}hrt ⇩praktisch u.a. ein PUSH PC (PC? Program Counter, noch'n Register!) ⇩aus und ein RET ein POP PC. Wenn ich also in einer Unterroutine nicht ⇩genauso viele PUSHs wie POPs habe, kommt der CPC mit seiner ⇩R}cksprungadresse durcheinander und landet zu 99% im Nirvana (yeah!).
Und wo ihr schonmal im Multi-Register-Lern-Modus seid, hier noch die ⇩letzten paar:
R-Register:
Das ist das Refresh-Register, l{~t sich mit dem Akku lesen und ⇩schreiben, also nur LD A,R und LD R,A. Das Refresh-Register ist ⇩blablabla - RAM-Refresh. .... Es ist praktisch ein Z{hler und wird ⇩kontinuiertlich hochgez{hlt. Verwendung als Timer und f}r ⇩Zufallszahlen.
I-Register:
Das ist das Interrupt-Register. Genauso wie beim R-Register l{~t sich ⇩mit LD A,I und LD I,A darauf zugreifen. Dieses Register hat im CPC ⇩keine besondere Verwendung und kann als Zwischenspeicher f}r den Akku ⇩benutzt werden.
Etwas Allgemeines:
Alle Ladebefehle beeinflussen die Flags nicht(!),
au~er LD A,R und LD A,I !!!
Im n{chsten Teil gibt's was }ber Bit-, Rotations- und Schiebe-Befehle.
Assemblerkurs Teil 5
Jetzt wird's heftig! Im 5.Teil des Assemblerkurses gebe ich euch den ⇩Rest! Ich meine, ich erkl{re euch die restlichen Assemblerbefehle. ⇩Danach, im 6.Teil, gibt's eine Komplett}bersicht mit Kurzerkl{rung, ⇩inclusive Flagver{nderungen und Laufzeiten.
Auf geht's:
Die Rotationsbefehle:
RL s (s=A,B,C,D,E,H,L,(HL),(IX+Offs),(IY+Offs))
Rotiert s nach links durch das Carry-Flag, d.h. der Inhalt des
Carry-Flags wird Bit 0 (b0), Bit 0 wird Bit 1 (b1), b1 wird b2, ...
b6 wird b7 und b7 kommt ins Carry.
Beispiel:
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 0 1 1 0 1 1 0 1 1
RL B
B: 1 1 0 1 1 0 1 1 0
Die Bits scrollen praktisch nach links durch das B-Register unter ⇩miteinbeziehen der Carry-Flags.
RR s
Rotiert s nach rechts.
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 0 1 1 0 1 1 0 1 1
RR B
B: 1 0 1 1 0 1 1 0 1
RLC s
Rotiert nur s, links, ohne Carry, d.h. b0 wird nicht vom Carry ⇩bestimmt, sondern von b7. b7 wird zus{tzlich noch ins Carry geschoben.
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 0 1 1 0 1 1 0 1 x
RLC B
B: 1 1 0 1 1 0 1 0 0
RRC s
Rotiert s rechts, ohne Carry. Das Carry-Flag wird von b0 bestimmt.
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 0 1 1 0 1 1 0 1 x
RRC B
B: 1 0 1 1 0 1 1 0 1
Die gerade besprochenen Befehle existieren auch als Spezialbefehl f}r ⇩den Akku. Sie sind k}rzer und schneller. Die herk|mmlichen ⇩Befehle beeinflussen allerdings alle Flags, die speziellen Befehle mit ⇩dem Akku greifen nur auf das Carry-Flag zu.
Die Akkubefehle lauten RLA, RRA, RLCA, RRCA. Es ist also ein ⇩Unterschied, ob ihr RLA schreibt oder RL A.
RLD
rotiert Nibble-weise den Inhalt der Speicherzelle, die durch HL ⇩angegeben wird mit dem Low-Nibble des Akkus nach links.
A (HL)
xxxxaaaa bbbbcccc
RLD
xxxxbbbb ccccaaaa
Beispiel: A (HL)
&79 &F3
RLD
&7F &39
RRD
rotiert (HL) und A Nibble-weise rechts.
A (HL)
xxxxaaaa bbbbcccc
RRD
xxxxcccc aaaabbbb
Beispiel: A (HL)
&79 &F3
RRD
&73 &9F
Schiebebefehle:
Hiervon gibt's nur drei:
SLA s
Schiebt die Bits von s nach links, Bit 7, welches durch die ⇩Schiebeaktion herausf{llt, wird vom Carry-Flag aufgefangen. Von rechts ⇩kommt eine Null herein.
Carry b7 b6 b5 b4 b3 b2 b1 b0
B: x 0 1 1 0 1 1 0 1
SLA B
B: 0 1 1 0 1 1 0 1 0
SRA s
Schiebt die Bits von s nach rechts. Das Carry wird vom herausfallenden ⇩Bit 0 bestimmt. Es wird keine Null von links hereingeschoben(!), ⇩sondern b7 bleibt bestehen.
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 0 1 1 0 1 1 0 1 x
SRA B
B: 0 0 1 1 0 1 1 0 1
Beispiel f}r b7=1:
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 1 0 0 1 0 0 1 0 x
SRA B
B: 1 1 0 0 1 0 0 1 0
SRL s
genauso wie SRA, nur wird b7 gleich Null, d.h. von links wird eine ⇩Null hereingeschoben.
b7 b6 b5 b4 b3 b2 b1 b0 Carry
B: 1 0 0 1 0 0 1 0 x
SRA B
B: 0 1 0 0 1 0 0 1 0
Bit-Befehle:
Diese Befehle sind eigentlich recht einfach zu verstehen.
SET Bitnr.,s
Setzt Bit n in s
Beispiel: b7 b6 b5 b4 b3 b2 b1 b0
E: 1 1 0 1 0 0 0 0
SET 2,E
E: 1 1 0 1 0 (1) 0 0
RES Bitnr.,s
L|scht Bit n ind s
Beispiel: b7 b6 b5 b4 b3 b2 b1 b0
E: 1 1 0 1 0 0 0 0
RES 4,E
E: 1 1 0 (0) 0 0 0 0
BIT Bitnr.,s
Testet einen Bitzustand. Ist das getestete Bit gleich eins, so ist das ⇩Zero-Flag gel|scht (NZ), ist es gleich Null, so ist das Zero-Flag ⇩gesetzt (Z).
Beispiel: b7 b6 b5 b4 b3 b2 b1 b0
E: 0 1 1 0 0 1 0 0
BIT 4,E
JR Z,loop
In diesem Fall w}rde der Computer nach 'loop' springen.
Sonstiges...
CP s (s=A,B,C,D,E,H,L,(HL),(IX+Offs),(IY+Offs),8-Bit-Zahl)
Der CP-Befehl ist im Prinzip ein SUB-Befehl, nur da~ das Ergebnis ⇩nicht im Akku gespeichert wird. Er dient zum Vergleichen von zwei
8-Bit-Werten.
CP B vergleicht z.B. den Akku mit B. Ist A kleiner als B (A<B), so ⇩ist das Carry-Flag gesetzt (C), da das Subtraktionsergebnis von A-B ⇩null unterschreitet. Ist A gr|~er oder gleich B (A>=B), so ist das ⇩Carry nicht gesetzt (NC). Ist A gleich B (A=B), dann ist das Zero-Flag ⇩gesetzt (Z), da in diesem Fall A-B=0 ist. Ist A ungleich B (A<>B), ⇩dann ist das Zero-Flag gel|scht (NZ).
CP 8
JR C,mark hie~e springe wenn A<8
JR NC,mark hie~e springe wenn A>=8
JR Z,mark hie~e springe wenn A=8
JR NZ,mark hie~e springe wenn A<>8
Schaut euch folgendes Programm an:
loop LD A,(HL)
LD (DE),A
INC HL
INC DE
DEC BC
LD A,B
OR C
JR NZ,loop
Dieses Programm ist eine Kopierschleife. Es kopiert von (HL) nach ⇩(DE). In BC steht die Anzahl der Bytes, die kopiert werden sollen.
Daf}r gibt's einen Spezialbefehl: LDIR
HL ist die Adresse der Quelldaten
DE ist die Zieladresse
BC ist die Anzahl der Bytes, die kopiert werden sollen
Die Zeiger (HL und DE) werden dabei nach jedem kopierten Byte um eins ⇩erh|ht, der Code wird praktisch 'vorw{rts' kopiert.
loop LD A,(HL)
LD (DE),A
DEC HL
DEC DE
DEC BC
LD A,B
OR C
JR NZ,loop
Ist dasselbe wie der Befehl LDDR. Dieser Befehl funktioniert genauso ⇩wie LDIR, nur werden die Bytes praktisch 'r}ckw{rts' kopiert, da die ⇩Pointer (HL und DE) jeweils verringert werden.
Beispiel:
ORG &5000
LD HL,&8000 ;von &8000
LD DE,&C000 ;nach &C000
LD BC,&4000 ;&4000 Bytes
LDIR ;kopieren
RET
kopiert euch den Bereich von &8000-&BFFF in den Bildschirmspeicher
per LDIR.
Mit LDDR sieht das so aus:
ORG &5000
LD HL,&BFFF ;Quelle &BFFF
LD DE,&FFFF ;Ziel &FFFF (Ende des Bildschirmspeichers)
LD BC,&4000 ;&4000 Bytes
LDDR ;kopieren
RET
SCF
Setzt das Carry-Flag.
CCF
Komplementiert (invertiert) das Carry-Flag.
CPL
Invertiert den Akku.
Beispiel: A: 10110011
CPL
A: 01001100
NEG
Negiert den Akku A=0-A bzw. A=-A
Wenn ihr z.B. B-A rechnen wollt, so m}~te man schreiben:
LD C,A
LD A,B
SUB C
stattdessen kann man auch schreiben
SUB B ;A-B
NEG ;-(A-B)=B-A!
DI
sperrt den Interrupt.
EI
l{~t den Interrupt wieder zu.
EX HL,DE (bzw. im Assemblerbuch steht EX DE,HL)
vertauscht HL und DE.
EX (SP),HL
EX (SP),IX
EX (SP),IY
vertauscht HL,IX,IY mit dem obersten Stapelelement. Wozu ist das gut? ⇩Wenn man z.B. BC mit IX vertausche will, so geht das so:
PUSH BC
EX (SP),IX
POP BC
EX AF,AF'
Im Z80 existieren die Register A,F,B,C,D,E,H,L doppelt. Mit diesem ⇩Befehl wird der Akku samt Flags mit seinem Doppelg{nger vertauscht.
Diesen, sowie den nachfolgenden Befehl solltet ihr nur benutzen, wenn ⇩ihr den CPC auch wirklich im Griff habt, da der zweite Registersatz ⇩vom Betriebsystem benutzt wird!
EXX
Vertauscht BC, DE und HL mit den Registern aus dem zweiten Satz.
NOP (im Speicher ein Null-Byte)
macht garnichts.
HALT
h{lt die CPU (den Z80) an. (H{?). Und zwar solange bis der n{chste ⇩Interrupt kommt.
RST x (x=&00,&08,&10,&18,&20,&28,&30,&38)
Der RST-Befehl springt an die Adresse x. Dies ist ein schneller
CALL-Befehl, da ein RST nur ein Byte lang ist (CALL = 3 Bytes, ⇩Befehlsbyte + zwei Bytes f}r die Adresse)
Wird vom Betriebsystem benutzt.
DAA
Dezimalanpassung des Akkumulators, ziemlich ungebr{uchlicher Befehl.
Beispiel: A: &09
ADD 1
DAA
A: &10 (Normalerweise w{re A=&0A)
Die Hexadezimalzahl sieht praktisch aus wie eine Dezimalzahl.
IN r,(C) (r=A,B,C,D,E,H,L)
L{dt r von der Hardware, die durch die Adresse des BC(!) Registers ⇩angegeben wird, eigentlich m}~te der Befehl also IN r,(BC) hei~en!
Beispiel:
LD B,&F5 ;Portadresse BC=&F5xx (Lowbyte ist nicht von
;Bedeutung)
frame IN A,(C) ;Wert holen
RRA ;b0 ins Carry rotieren
JR NC,frame ;Wiederholen bis Carry gesetzt ist
Diese Routine wartet auf das sogenannte Frame-Signal, das alle 50stel ⇩Sekunde ausgegeben wird.
IN A,(n)
L{dt den Akku von der Hardware, die durch A (Highbyte) und den Wert n ⇩(Lowbyte) angegeben wird.
Beispiel:
frame LD A,&F5
IN A,(0)
RRA
JR NC,frame
s.o.
OUT (c),r (r=A,B,C,D,E,H,L)
Gibt r an die Hardware aus, die durch die Adresse aus BC angegeben ⇩wird. Beispiel gibt's sp{ter mal.
Einige wenige Befehle fehlen noch. Diese werdet ihr aber in der ⇩versprochenen Gesamt}bersicht finden. Bis dann!